/*
cAndy Apple Core: An Apple //e emulator engine
    - based almost entirely on AppleWin

Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2008, Tom Charlesworth, Michael Pohoreski, Nick Westgate
Copyright (C) 2009-2010, Garnet Ulrich

cAndy Apple Core is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

cAndy Apple Core is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with cAndy Apple Core; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "StdAfx.h"
//#pragma  hdrstop

#include <jni.h> //gu - for jni sdk to ndk interface


BYTE    CARom[0x4000]; //loading zone where jni loads the rom in from java - gets read by memory.cpp meminitialize
BYTE * pCARom = &CARom[0];
eApple2Type CARomType;
int	CARomSize;

eApple2Type	g_Apple2Type	= A2TYPE_APPLE2EEHANCED;

DWORD     cyclenum          = 0;			// Used by SpkrToggle() for non-wave sound
DWORD     emulmsec          = 0;
static DWORD emulmsec_frac  = 0;
bool      g_bFullSpeed      = false;

//=================================================


DWORD		g_dwSpeed		= SPEED_NORMAL;	// gu - never changes in cAndy Apple
double		g_fCurrentCLK6502 = CLK_6502;

DWORD       g_dwCyclesThisFrame = 0;

extern void KeybSetKeycode(int);

eCPU		g_ActiveCPU = CPU_6502;


//===========================================================================

void ContinueExecution() { // executes a single time slice - gu
	const double fUsecPerSec        = 1.e6;
	const UINT nExecutionPeriodUsec = 1000;		// 1.0ms
	const double fExecutionPeriodClks = g_fCurrentCLK6502 * ((double)nExecutionPeriodUsec / fUsecPerSec);

	g_bFullSpeed = true;

	int nCyclesToExecute = (int) fExecutionPeriodClks;
	if(nCyclesToExecute < 0)  //this is not the main way of limiting execution
		nCyclesToExecute = 0;

	//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&&&&&&&&&&&&&&&&&&&&!!!!!!!!!!!!!!!!!
	DWORD dwExecutedCycles = CpuExecute(nCyclesToExecute); // <- !!!!!!! this is where it executes clock cycles in CPU.cpp
	//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&&&&&&&&&&&&&&&&&&&&!!!!!!!!!!!!!!!!!

	g_dwCyclesThisFrame += dwExecutedCycles;

	cyclenum = dwExecutedCycles;

	DiskUpdatePosition(dwExecutedCycles);
	JoyUpdatePosition();
	VideoUpdateVbl(g_dwCyclesThisFrame);
	SpkrUpdate(cyclenum);

	const DWORD CLKS_PER_MS = (DWORD)g_fCurrentCLK6502 / 1000;

	emulmsec_frac += dwExecutedCycles;
	if(emulmsec_frac > CLKS_PER_MS) {
		emulmsec += emulmsec_frac / CLKS_PER_MS;
		emulmsec_frac %= CLKS_PER_MS;
	}

	//
	// DETERMINE WHETHER THE SCREEN WAS UPDATED, THE DISK WAS SPINNING,
	// OR THE KEYBOARD I/O PORTS WERE BEING EXCESSIVELY QUERIED THIS CLOCKTICK
	VideoCheckPage(0);
	BOOL screenupdated = VideoHasRefreshed();
	BOOL systemidle    = 0;

	if(g_dwCyclesThisFrame >= dwClksPerFrame)
	{
	    g_dwCyclesThisFrame -= dwClksPerFrame;

	    VideoUpdateFlash();

	    static BOOL  anyupdates     = 0;
	    static BOOL  lastupdates[2] = {0,0};

	    anyupdates |= screenupdated;

	    if ((!anyupdates) && (!lastupdates[0]) && (!lastupdates[1]) && VideoApparentlyDirty())
	    {
	        VideoCheckPage(1);
	        VideoRefreshScreen();
	        screenupdated = 1;
	    }

	    lastupdates[1] = lastupdates[0];
	    lastupdates[0] = anyupdates;
	    anyupdates     = 0;
	}
}

//===========================================================================
void CtrlReset()
{
    if (!IS_APPLE2)
        MemResetPaging();
    DiskReset();
    KeybReset();
    if (!IS_APPLE2)
        VideoResetState();  // Switch Alternate char set off
    CpuReset();
}

//===========================================================================

void SetCurrentCLK6502()
{
	static DWORD dwPrevSpeed = (DWORD) -1;
	if(dwPrevSpeed == g_dwSpeed)
		return;
	dwPrevSpeed = g_dwSpeed;
	g_fCurrentCLK6502 = CLK_6502;
}


//===========================================================================
void CAInitialization () {
    g_Apple2Type = (eApple2Type) A2TYPE_APPLE2EEHANCED;
    g_dwSpeed = SPEED_NORMAL;
    SetCurrentCLK6502();
}

//===========================================================================

DWORD CACoreGetSnapshot(SS_CACore* pSS)  //used for load
{
    pSS->g_Apple2Type           = g_Apple2Type;
    pSS->cyclenum               = cyclenum;
    pSS->emulmsec               = emulmsec;
    pSS->emulmsec_frac          = emulmsec_frac;
    pSS->g_bFullSpeed           = g_bFullSpeed;
    pSS->g_fCurrentCLK6502      = g_fCurrentCLK6502;
    pSS->g_dwCyclesThisFrame    = g_dwCyclesThisFrame;

    return 0;
}

DWORD CACoreSetSnapshot(SS_CACore* pSS) //used for save
{
    g_Apple2Type            = pSS->g_Apple2Type;
    cyclenum                = pSS->cyclenum;
    emulmsec                = pSS->emulmsec;
    emulmsec_frac           = pSS->emulmsec_frac;
    g_bFullSpeed            = pSS->g_bFullSpeed;
    g_fCurrentCLK6502       = pSS->g_fCurrentCLK6502;
    g_dwCyclesThisFrame     = pSS->g_dwCyclesThisFrame;

    return 0;
}

//===========================================================================

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env;
    if ((vm)->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
        return -1;

    return JNI_VERSION_1_4;
}


extern "C"
JNIEXPORT void JNICALL//load up the apple2e rom into a buffer that CACore then uses to load the rom areas
Java_com_zerohoh_candyapple_CAView_jniloadrom( JNIEnv* env, jobject thiz, jobject jnirom, jint romtype) {
        char *TEMPROM = (char*)(env)->GetDirectBufferAddress(jnirom);
        if (romtype == 4) {
        	memcpy(CARom,TEMPROM,16384);  //loads up the rom passed from java into CARom which gets used in memory.cpp meminitialize
        	CARomType = A2TYPE_APPLE2EEHANCED;
        	CARomSize = 16384;
        }
	return;
}

extern "C"
JNIEXPORT void JNICALL //save a diskette back to java
Java_com_zerohoh_candyapple_CAView_jnisavediskette( JNIEnv *env, jobject thiz, jobject jnidisk, jint drive, jint disksize) {
        char *TEMPDISK = (char*)(env)->GetDirectBufferAddress(jnidisk);
        CADiskSave (drive - 1, TEMPDISK, disksize);
    return;
}

extern "C"
JNIEXPORT void JNICALL //save a diskette back to java - used for saving state
Java_com_zerohoh_candyapple_State_jnisavediskette( JNIEnv *env, jobject thiz, jobject jnidisk, jint drive, jint disksize) {
        char *TEMPDISK = (char*)(env)->GetDirectBufferAddress(jnidisk);
        CADiskSave (drive - 1, TEMPDISK, disksize);
    return;
}

extern "C"
JNIEXPORT void JNICALL //load diskette bytes into jni as part of loading a saved state
Java_com_zerohoh_candyapple_State_jniloaddiskettebytes( JNIEnv *env, jobject thiz, jobject jnidisk, jint drive, jint disksize) {
        char *TEMPDISK = (char*)(env)->GetDirectBufferAddress(jnidisk);
        CADiskLoadBytes (drive - 1, TEMPDISK, disksize);
    return;
}

extern "C"
JNIEXPORT void JNICALL //load up a diskette into CACore
Java_com_zerohoh_candyapple_CAView_jniloaddiskette( JNIEnv *env, jobject thiz, jobject jnidisk, jint drive, jstring filename, jint rw, jint len) {
        char *TEMPDISK = (char*)(env)->GetDirectBufferAddress(jnidisk);
        char tempfile[100] = {0};
        const char *bogo = env->GetStringUTFChars(filename, 0);
        strcpy(tempfile,bogo);
        if (strcmp(tempfile , "something.dsk") == 0) {
            DiskInsert (drive - 1, TEMPDISK, 143360, !rw, tempfile);  //use !rw because rw is logical opposite of writeprotected
        } else if (strcmp(tempfile , "something.bin") == 0) {
            DiskInsert (drive - 1, TEMPDISK, len, !rw, tempfile);  //.bin
        } else if (strcmp(tempfile , "something.po") == 0) {
            DiskInsert (drive - 1, TEMPDISK, 143360, !rw, tempfile);  //.po
        } else {
            DiskInsert (drive - 1, TEMPDISK, 232960, !rw, tempfile);  //must be a .nib file
        }
        env->ReleaseStringUTFChars(filename, bogo);
	return;
}

extern "C"
JNIEXPORT void JNICALL //take diskette out of drive
Java_com_zerohoh_candyapple_CAView_jniejectdiskette( JNIEnv* env, jobject thiz, jint drive) {
    DiskEject(drive-1);
	return;
}

extern "C"
JNIEXPORT void JNICALL //reset
Java_com_zerohoh_candyapple_CAView_jnireset( JNIEnv* env, jobject thiz) {
    CtrlReset(); // this is done with CTRL and "run" button in applewin in frame.cpp
	return;
}

extern "C"
JNIEXPORT void JNICALL //joystick
Java_com_zerohoh_candyapple_CAView_jnijoystick( JNIEnv* env, jobject thiz,
		 jint jnijoy0pos, jint jnijoy1pos, jint jnijoy0button, jint jnijoy1button) {
    CASetJoystick0(jnijoy0pos,jnijoy1pos,jnijoy0button,jnijoy1button);
	return;
}

extern "C"
JNIEXPORT void JNICALL //joystick - candyapplemain
Java_com_zerohoh_candyapple_CandyAppleMain_jnijoystickcam( JNIEnv* env, jobject thiz,
		 jint jnijoy0pos, jint jnijoy1pos, jint jnijoy0button, jint jnijoy1button) {
    CASetJoystick0(jnijoy0pos,jnijoy1pos,jnijoy0button,jnijoy1button);
	return;
}

extern "C"
JNIEXPORT jint JNICALL //cpu run
Java_com_zerohoh_candyapple_EmAppleII_jnicpurun( JNIEnv* env, jobject thiz, jint numcyclestorun, jint jnikeypress, jintArray jnidisplaybuffer) {
    CAenv = env;
    CAdisplaybuffer = jnidisplaybuffer;
    if (jnikeypress > 0) {
        KeybSetKeycode(jnikeypress);
    }
    int foo;
    for (foo = 0;foo < 50;foo++) {
        ContinueExecution();  //do 50 1ms slices
    }
	return DiskIsSpinning ();
}

extern "C"
JNIEXPORT void JNICALL //load defaultrom - sets up the machine to run...
Java_com_zerohoh_candyapple_CAView_jniloaddefaultrom( JNIEnv* env, jobject thiz) {
    CARomType = A2TYPE_STUB; //this tells Memory.cpp meminitialize to load up the stub rom
	return;
}

extern "C"
JNIEXPORT void JNICALL //by this point we should have loaded the rom
Java_com_zerohoh_candyapple_CAView_jnistartup( JNIEnv* env, jobject thiz) {
    MemPreInitialize(); //sets up ioread function pointers
    ImageInitialize(); //allocates a workbuffer (diskimage.cpp)
    CAInitialization(); //some of the stuff that was in loadconfiguration is in our new function - cpu type and speed
    JoyInitialize(); //from winmain
    MemInitialize(); //from winmain
    DiskReset(); //from winmain
    DiskBoot();
    VideoInitialize();
    SpkrInitialize();
    return;
}

extern "C"
JNIEXPORT void JNICALL //by this point we should have loaded the rom
Java_com_zerohoh_candyapple_CAView_jniPrepRestore( JNIEnv* env, jobject thiz) {
    MemPreInitialize(); //sets up ioread function pointers
    ImageInitialize(); //allocates a workbuffer (diskimage.cpp)
    CAInitialization(); //some of the stuff that was in loadconfiguration is in our new function - cpu type and speed
    MemInitialize(); //from winmain
    VideoInitialize();
    SpkrInitialize();
    return;
}

extern "C"
JNIEXPORT jint JNICALL //is the floppy active?
Java_com_zerohoh_candyapple_CAView_jnidriveactive( JNIEnv* env, jobject thiz, jint drive ) {
	return (DiskActivity(drive - 1));
}

extern "C"
JNIEXPORT void JNICALL //force screen redraw
Java_com_zerohoh_candyapple_CandyAppleMain_jniredraw( JNIEnv* env, jobject thiz) {
    g_CARedrawFlag = 3; //this flag is used to make good and sure it gets updated.  could probably be set to 2.  without this mechanism
                        // sometimes timing of this call is such that the g_VideoForceFullRedraw value gets set back to 0 without a redraw
    g_VideoForceFullRedraw = 1;
	return;
}

extern "C"
JNIEXPORT jint JNICALL //push sound back to java
Java_com_zerohoh_candyapple_EmAppleII_jniGrabSoundBuf( JNIEnv* env, jobject obj, jbyteArray arr1) {

   BYTE *arrPtr1 = (BYTE *)env->GetPrimitiveArrayCritical(arr1,0);
   int blammo;
   for (blammo=0;blammo < g_nBufferIdx;blammo++) {
       if (g_pSpeakerBuffer[blammo] == 0x80) {
           arrPtr1[blammo] = 0x80;
       } else {
           arrPtr1[blammo] = 0x60;
       }
   }
   env->ReleasePrimitiveArrayCritical(arr1, arrPtr1, 0);
   int returnVal = g_nBufferIdx;
   g_nBufferIdx = 0;
   return returnVal; //return how many bytes there were in buffer
}

extern "C"
JNIEXPORT jint JNICALL //just throw away the accumulated sound
Java_com_zerohoh_candyapple_EmAppleII_jniDumpSoundBuf( JNIEnv* env, jobject obj) {
    g_nBufferIdx = 0;
}

extern "C"
JNIEXPORT void JNICALL //save the jni state
Java_com_zerohoh_candyapple_CandyAppleMain_jniSaveState( JNIEnv* env, jobject thiz, jstring filename) {

    char tempfilename[100] = {0};
    const char *bogo = env->GetStringUTFChars(filename, 0);
    strcpy(tempfilename,bogo);

    Snapshot_SaveState(tempfilename);
}

extern "C"
JNIEXPORT void JNICALL //load the jni state
Java_com_zerohoh_candyapple_CandyAppleMain_jniLoadState( JNIEnv* env, jobject thiz, jstring filename) {

    char tempfilename[100] = {0};
    const char *bogo = env->GetStringUTFChars(filename, 0);
    strcpy(tempfilename,bogo);

    Snapshot_LoadState(tempfilename);
}

